-------------Good Thinking!------------
-------Cause and Effect Level Two------
A 4am crack                  2019-04-05
---------------------------------------

Name: Good Thinking! Cause and Effect
  Level Two
Genre: educational
Year: 1986
Publisher: Hoffman Educational Systems
Platform: Apple ][+ or later
Media: 5.25-inch disk
Sides: 1
OS: DOS 3.3
Previous cracks: none

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but copy boots to title
  screen then reboots

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Copy ][+ nibble editor
  nothing suspicious, looks like a
  standard 16-sector DOS 3.3 disk

Disk Fixer
  T00 is a bit odd because the sector
  order is non-standard.
  T11 is also odd -- there's definitely
  a DOS 3.3 style disk catalog there,
  but it starts on sector 7 instead of
  sector $0F, so third-party utilities
  don't find it. At any rate, no
  specific clues about why it would
  reboot so late in the process. Disks
  do not spontaneously reboot unless
  someone tells them to.

Why didn't any of my copies work?
  unclear -- runtime protection check?

Next steps:

  1. Split up
  2. Search for clues
  3. Wait no, that's Scooby Doo

                   ~

               Chapter 1
           Unlucky In Cards


Since my copy goes down a different
code path than the original, I'm
guessing there is a runtime protection
check somewhere. One thing that all
protection checks have in common is
they need to turn on the drive motor by
accessing a specific address in the
$C0xx range. For slot 6, it's $C0E9,
but to allow disks to boot from any
slot, developers usually use code like
this:

  LDX <slot number x 16>
  LDA $C089,X

There's nothing that says where the
slot number has to be, although the
disk controller ROM routine uses zero
page $2B and lots of disks just reuse
that. There's also nothing that says
you have to use the X-register as the
index, or that you must use the
accumulator as the load register. But
most RWTS code does, out of convention
I suppose (or possibly fear of messing
up such low-level code in subtle ways).

Also, since developers don't actually
want people finding their protection-
related code, they may try to encrypt
it or obfuscate it to prevent people
from finding it. But eventually, the
code must exist and the code must run,
and it must run on my machine, and I
have the final say on what my machine
does or does not do.

But sometimes you get lucky.

Turning to my trusty Disk Fixer sector
editor, I search the non-working copy
for "BD 89 C0", which is the opcode
sequence for "LDA $C089,X".

[Disk Fixer]
  ["F"ind]
    ["H"ex]
      ["BD 89 C0"]

                 --v--

------------- DISK SEARCH -------------

$00/$0C-$16   $00/$0E-$4F

                 --^--

Again, track 0 is out of order, so it
turns out that the match on sector $0E
is just the regular part of the DOS 3.3
RWTS. But the match on sector $0C is
much more suspicious.

                 --v--

T00,S0C
----------- DISASSEMBLY MODE ----------
; set up the RWTS parameter table with
; track 7
0000:A9 07          LDA   #$07
0002:8D EC B7       STA   $B7EC

; 0 = "seek" command
0005:A9 00          LDA   #$00
0007:8D F4 B7       STA   $B7F4
000A:8D F2 B7       STA   $B7F2

; seek to track 7
000D:20 E3 03       JSR   $03E3
0010:20 D9 03       JSR   $03D9

; turn on drive motor manually (this is
; how I found this routine)
0013:AE E9 B7       LDX   $B7E9
0016:BD 89 C0       LDA   $C089,X

; counters of some sort
0019:A9 00          LDA   #$00
001B:85 3C          STA   $3C
001D:A9 10          LDA   #$10
001F:85 3E          STA   $3E
0021:C6 3E          DEC   $3E
0023:D0 06          BNE   $002B
0025:A9 10          LDA   #$10
0027:85 3E          STA   $3E
0029:D0 7E          BNE   $00A9

; get and parse the next available
; address field
002B:20 44 B9       JSR   $B944
002E:B0 F1          BCS   $0021

; until we find a specific sector given
; in zero page $3C (currently #$00)
0030:A5 2D          LDA   $2D
0032:C5 3C          CMP   $3C
0034:D0 EB          BNE   $0021

; look for third nibble of address
; epilogue (the routine at $B944
; verifies the $DE and $AA only)
0036:BD 8C C0       LDA   $C08C,X
0039:10 FB          BPL   $0036
003B:C9 EB          CMP   #$EB
003D:D0 6A          BNE   $00A9

; burn cycles by calling an "RTS" a
; few times, and doing nothing in a
; variety of ways
003F:20 C3 BC       JSR   $BCC3
0042:20 C3 BC       JSR   $BCC3
0045:EA             NOP
0046:EA             NOP
0047:EA             NOP
0048:EA             NOP
0049:EA             NOP

; next nibble needs to be #$F2
004A:BD 8C C0       LDA   $C08C,X
004D:10 FB          BPL   $004A
004F:C9 F2          CMP   #$F2
0051:D0 56          BNE   $00A9

; do this for every sector
0053:E6 3C          INC   $3C
0055:A5 3C          LDA   $3C
0057:C9 10          CMP   #$10
0059:D0 D0          BNE   $002B

; do this for every track (down to 0)
005B:CE EC B7       DEC   $B7EC
005E:10 AD          BPL   $000D

; another counter
0060:A0 40          LDY   #$40
0062:8C F2 B7       STY   $B7F2
0065:88             DEY
0066:D0 05          BNE   $006D
0068:CE F2 B7       DEC   $B7F2
006B:F0 49          BEQ   $00B6

; look for specific nibble sequence
; $CA $D2 $A9
006D:BD 8C C0       LDA   $C08C,X
0070:10 FB          BPL   $006D
0072:C9 CA          CMP   #$CA
0074:D0 EF          BNE   $0065
0076:EA             NOP
0077:BD 8C C0       LDA   $C08C,X
007A:10 FB          BPL   $0077
007C:C9 D2          CMP   #$D2
007E:D0 F2          BNE   $0072
0080:EA             NOP
0081:BD 8C C0       LDA   $C08C,X
0084:10 FB          BPL   $0081
0086:C9 A9          CMP   #$A9
0088:D0 E8          BNE   $0072

; read a 4-and-4-encoded value after
; the custom nibble sequence
008A:A0 01          LDY   #$01
008C:BD 8C C0       LDA   $C08C,X
008F:10 FB          BPL   $008C
0091:2A             ROL
0092:85 2D          STA   $2D
0094:BD 8C C0       LDA   $C08C,X
0097:10 FB          BPL   $0094
0099:25 2D          AND   $2D

; this will actually overwrite the
; zero page addresses we were using
; earlier
009B:99 3C 00       STA   $003C,Y
009E:88             DEY
009F:10 EB          BPL   $008C

; turn off drive motor and reset DOS
; zero page addresses
00A1:BD 88 C0       LDA   $C088,X
00A4:A9 00          LDA   #$00
00A6:85 48          STA   $48
00A8:60             RTS

; failure path is here -- if anything
; fails, increment the death counter
; and retry up to 16 times
00A9:EE F2 B7       INC   $B7F2
00AC:AD F2 B7       LDA   $B7F2
00AF:C9 10          CMP   #$10
00B1:D0 A0          BNE   $0053

; give up, turn off drive motor, and
; jump to $B6B3
00B3:BD 88 C0       LDA   $C088,X
00B6:4C B3 B6       JMP   $B6B3

                 --^--

Just for giggles, here is the routine
at $B6B3:

                 --v--

T00,S00
----------- DISASSEMBLY MODE ----------
; copy to zero page
00B3:A0 00          LDY   #$00
00B5:B9 B2 B6       LDA   $B6B2,Y
00B8:99 00 00       STA   $0000,Y
00BB:88             DEY
00BC:D0 F7          BNE   $00B5

; continue from there
00BE:4C 0F 00       JMP   $000F

; wipe all of main memory
00C1:99 00 BF       STA   $BF00,Y
00C4:88             DEY
00C5:D0 FA          BNE   $00C1
00C7:C6 11          DEC   $11
00C9:D0 F6          BNE   $00C1

; and reboot
00CB:6C FC FF       JMP   ($FFFC)

                 --^--

...which is exactly what happened on my
non-working copy.

                   ~

               Chapter 2
           The Space Between


The heart of this protection routine is
burning CPU cycles after the $EB nibble
and requiring that the next nibble we
read is $F2. This is only true because
there are two hidden timing bits after
the $EB nibble, which most disks don't
have and bit copiers can't detect.

Here is track 7 in the Copy II Plus
nibble editor. Timing bits are usually
indicated with inverse characters, but
I have converted them to "+" signs:

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 07  START: 1F14  LENGTH: 180D

1EF0: FF+FF+FF+FF+FF+FF+FF+CA   VIEW
1EF8: D2 A9 AA AA AA AA AA AA
1F00: AA AA AA AA AA AA FC FF+
1F08: FF+FF+FF+CF F3 FC FF+FF+
1F10: FF+FF+FF+FF+D5 AA 96 FF  <-1F14
1F18: FE AB AF AA AA FE FB DE
1F20: AA EB+F2 FF F9 FE FF+FF+
1F28: FF+FF+D5 AA AD DF FC FC
1F30: FC FE FF FF FF FF FF FF

                 --^--

At offset $1F21, the $EB has a timing
bit after it. OK, fine, bit copiers can
detect and reproduce that. But wait!
It's actually TWO timing bits. Bit
copiers can't tell the difference
between 1 and 2 timing bits -- there
literally isn't enough time at 1 MHz.
All the self-sync nibbles ($FF values
between the address field and the data
field) usually have 2 timing bits after
them, so the bit copiers will fake it
and assume 2 bits there. But if there
happen to be 2 timing bits after any
other nibble, bit copiers will only
detect 1 and only reproduce 1, and this
protection check is designed to tell
the difference.

                   ~

               Chapter 2
        Automate All The Things


Despite the fact that the second part
of the protection routine reads values
and stores them in zero page, the
caller does not appear to check these.
(I checked; they're zeroes.) I put an
"RTS" at the beginning of the routine,
and the program finished booting
without complaint.

As I have several other disks from the
same publisher that share the same
protection, I applied the same patch to
all of them, and they all worked. So
we're going to call this a side-effect-
free protection check.

FURTHERMORE since I have more than one
disk that shares the same protection,
it's a perfect candidate for Passport.
Because why crack one thing when you
can crack all the things?

Here is what the Passport log will look
like:

                 --v--

Reading from S6,D1
Using built-in RWTS
Writing to RAM disk
T00,S0C Found Hoffman protection check
T00,S0C,$00: A9 -> 60
Writing to S6,D2
Crack complete. Press any key

                 --^--

Quod erat liberandum.

---------------------------------------
A 4am crack                    No. 1981
------------------EOF------------------
